﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Input.Touch;
using Microsoft.Xna.Framework.Media;

namespace DARE
{
    public class CMusicMgr
    {

    #region identifier

        /// <summary>
        /// Not really usefull atm
        /// </summary>
        public enum EFadeOption
        {
            SOURCE, 
            TARGET, 
            CURRENT
        }

        /// <summary>
        /// Classic option for repeat:
        /// - NONE: repeat is desable
        /// - ALL: when it reach the end of the playlist return to the beginning and play
        /// - MUSIC: repeat one music
        /// </summary>
        public enum ERepeatOption
        {
            NONE, 
            ALL, 
            MUSIC
        }

        /// <summary>
        /// Classic play option:
        /// - ALL: play all the list
        /// - MUSIC: play only one music
        /// - SHUFFLE: random music
        /// </summary>
        public enum EPlayOption
        {
            ALL, 
            MUSIC, 
            SHUFFLE
        }

        /// <summary>
        /// Struct used for the fade effect between musics
        /// </summary>
        private struct SMusicFadeEffect
        {

        #region fields

            public float m_sourceVolume;
            public float m_targetVolume;

            private TimeSpan m_time;
            private TimeSpan m_duration;

        #endregion

        #region ctor

            /// <summary>
            /// Create the fade effect
            /// </summary>
            /// <param name="sourceVolume">Specify the start volume</param>
            /// <param name="targetVolume">Specify the volume at the end of the fade effect</param>
            /// <param name="duration">Duration of fade effect</param>
            public SMusicFadeEffect(float sourceVolume, float targetVolume, TimeSpan duration)
            {
                m_sourceVolume = sourceVolume;
                m_targetVolume = targetVolume;
                m_time = TimeSpan.Zero;
                m_duration = duration;
            }

        #endregion

        #region other

            /// <summary>
            /// Return the current volume when the fade effect is active
            /// </summary>
            public float Volume
            {
                get { return MathHelper.Lerp(m_sourceVolume, m_targetVolume, (float)m_time.Ticks / m_duration.Ticks); }
            }

            /// <summary>
            /// Update the time of the fade effect
            /// </summary>
            /// <param name="gameTime">Time elapsed since last frame</param>
            /// <returns>True if the update was usefull, false otherwise</returns>
            public bool Update(GameTime gameTime)
            {
                m_time += gameTime.ElapsedGameTime;
                if (m_time >= m_duration)
                {
                    m_time = m_duration;
                    return true;
                }
                return false;
            }

        #endregion

        }

    #endregion

    #region fields

        private ContentManager m_ContentManager = null;
        private Dictionary<string, Song> m_Songs = new Dictionary<string, Song>();
        private Dictionary<string, string> m_paths = new Dictionary<string, string>();
        private Song m_currentMusic = null;
        private string m_currentMusicName = null;
        private SMusicFadeEffect m_MusicFadeEffect;
        private bool m_isMusicPaused = false;
        private bool m_isMusicFading = false;
        private bool m_fadeBetweenMusic = false;
        private bool m_muteMusic = false;
        private float m_volume = 1.0f;
        private ERepeatOption m_repeatOption = ERepeatOption.NONE;
        private EPlayOption m_playOption = EPlayOption.MUSIC;
        private int m_musicIndex = 0;
        private Random m_rand = new Random();
        private TimeSpan m_fadeDuration = new TimeSpan(0, 0, 1);
        private string m_musicDirectory = "";

    #endregion

    #region properties

        /// <summary>
        /// Directory where the music is, this is used when the music path is not 
        /// specified in a method.
        /// </summary>
        public string MusicDirectory
        {
            get { return m_musicDirectory; }
            set { m_musicDirectory = value.EndsWith("/") ? value : value + "/"; }
        }

        /// <summary>
        /// Gets the paths of every musics loaded into this manager
        /// </summary>
        public Dictionary<string, string> MusicPaths { get { return m_paths; } }

        /// <summary>
        /// Get or set the music volume, this volume will slowly fade with the fade effect
        /// </summary>
        public float MusicVolume
        { 
            get { return MediaPlayer.Volume; }
            set { MediaPlayer.Volume = value; m_volume = value; }
        }

        /// <summary>
        /// Get the volume, but is independant from the fade effect
        /// </summary>
        public float Volume
        {
            get { return m_volume; }
        }

        /// <summary>
        /// Get if the music is active or not (stopped or null)
        /// </summary>
        public bool IsMusicActive
        {
            get { return (m_currentMusic != null) && (MediaPlayer.State != MediaState.Stopped); }
        }

        /// <summary>
        /// Get if the music is paused
        /// </summary>
        public bool IsMusicPaused
        {
            get { return (m_currentMusic != null) && m_isMusicPaused; }
            set 
            { 
                if (value == true)
                    PauseMusic();
                else
                    ResumeMusic();
            }
        }

        /// <summary>
        /// Get if the music is fading
        /// </summary>
        public bool IsMusicFading
        {
            get { return m_isMusicFading; }
        }

        /// <summary>
        /// Mute the music
        /// </summary>
        public bool MuteMusic
        {
            get { return m_muteMusic; }
            set { m_muteMusic = value; }
        }

        /// <summary>
        /// Get the current music name
        /// </summary>
        public string CurrentMusic
        {
            get { return m_currentMusicName; }
            private set { m_currentMusicName = value; }
        }

        /// <summary>
        /// Apply a fade effect for all music changements
        /// </summary>
        public bool FadeBetweenMusic
        {
            get { return m_fadeBetweenMusic; }
            set { m_fadeBetweenMusic = value; }
        }

        /// <summary>
        /// Get or set the play options
        /// </summary>
        public EPlayOption PlayOption
        {
            get { return m_playOption; }
            set { m_playOption = value; }
        }

        /// <summary>
        /// Get or set the repeat options
        /// </summary>
        public ERepeatOption RepeatOption
        {
            get { return m_repeatOption; }
            set { m_repeatOption = value; }
        }

        /// <summary>
        /// Get or set the fade duration
        /// </summary>
        public TimeSpan FadeDuration
        {
            get { return m_fadeDuration; }
            set { m_fadeDuration = value; }
        }

    #endregion

    #region ctor
        
        /// <summary>
        /// 
        /// </summary>
        /// <param name="game"></param>
        public CMusicMgr()
        {
            m_ContentManager = new ContentManager(CDare.Instance.Game.Content.ServiceProvider, CDare.Instance.Game.Content.RootDirectory);
        }

    #endregion

    #region init

        /// <summary>
        /// Load a music in the music manager by giving his name and path, fail if the music 
        /// is already in the playlist
        /// </summary>
        /// <param name="musicName">Name of the music to load</param>
        /// <param name="musicPath">Path of the music, if null, the property MusicDirectory + musicName will be used</param>
        /// <returns>True if the music was successfully loaded, false otherwise</returns>
        public bool LoadMusic(string musicName, string musicPath)
        {
            if (m_Songs.ContainsKey(musicName))
                return false;
            if (musicPath == null)
                musicPath = MusicDirectory + musicName;
            m_Songs.Add(musicName, m_ContentManager.Load<Song>(musicPath));
            m_paths.Add(musicName, musicPath);
            return true;
        }

        public bool LoadMusic(string musicName)
        {
            return LoadMusic(musicName, null);
        }

        /// <summary>
        /// Update the music manager with the game time
        /// Play option is over Repeat option
        /// </summary>
        public void Update(GameTime gameTime)
        {
            if ((m_currentMusic != null) && (MediaPlayer.State == MediaState.Stopped))
            {
                if (m_playOption == EPlayOption.MUSIC)
                {
                    m_currentMusic = null;
                    CurrentMusic = null;
                    m_isMusicPaused = false;
                }
                else
                {
                    if (m_repeatOption == ERepeatOption.ALL)
                    {
                        if (m_musicIndex >= m_Songs.Count)
                        {
                            m_musicIndex = 0;
                            PlayMusic(m_Songs.Keys.ElementAt(m_musicIndex));
                        }
                        else
                            NextMusic();
                    }
                    else if (m_repeatOption == ERepeatOption.MUSIC)
                        PlayMusic(m_Songs.Keys.ElementAt(m_musicIndex));
                }
            }
            if (m_isMusicFading && !m_isMusicPaused)
            {
                if ((m_currentMusic != null) && (MediaPlayer.State == MediaState.Playing))
                {
                    if (m_MusicFadeEffect.Update(gameTime))
                        m_isMusicFading = false;
                    MediaPlayer.Volume = m_MusicFadeEffect.Volume;
                }
                else
                    m_isMusicFading = false;
            }
        }

        /// <summary>
        /// Unload all the data in the music manager
        /// </summary>
        public void UnloadContent()
        {
            m_ContentManager.Unload();
            m_Songs.Clear();
            m_paths.Clear();
            MediaPlayer.Stop();
            m_currentMusic = null;
            m_currentMusicName = null;
        }

        /// <summary>
        /// Unload a specified music in the playlist and jump to the next, if no parameter is set, 
        /// unload the current music and jump to the next one
        /// </summary>
        /// <param name="musicName">Name of the music to unload</param>
        /// <returns>True if a music was removed, false otherwise</returns>
        public bool UnloadMusic(string musicName)
        {
            if (musicName != null)
            {
                if (m_Songs.ContainsKey(musicName))
                {
                    m_Songs.Remove(musicName);
                    m_paths.Remove(musicName);
                    m_currentMusic.Dispose();
                    NextMusic();
                    return true;
                }
                return false;
            }
            else
            {
                if (m_Songs.Count > 0)
                {
                    m_Songs.Remove(m_currentMusicName);
                    m_paths.Remove(m_currentMusicName);
                    m_currentMusic.Dispose();
                    NextMusic();
                    return true;
                }
            }
            return false;
        }

        public bool UnloadMusic()
        {
            return UnloadMusic(null);
        }

    #endregion

    #region controls

        /// <summary>
        /// Return the list of musics actually in the playlist
        /// </summary>
        /// <returns>List of musics by name</returns>
        public List<String> getPlayList()
        {
            return m_Songs.Keys.ToList();
        }

        /// <summary>
        /// Used to play a music, the music have to be loaded with LoadMusic
        /// </summary>
        /// <param name="musicName">Name of the music to play</param>
        /// <param name="loop">Tell the player to loop on this music, false by default</param>
        /// <returns>True if the music exists, false otherwise</returns>
        public bool PlayMusic(string musicName, bool loop)
        {
            if (CurrentMusic != musicName)
            {
                if (m_currentMusic != null)
                    MediaPlayer.Stop();
                if (!m_Songs.TryGetValue(musicName, out m_currentMusic))
                {
                    m_currentMusic = null;
                    m_currentMusicName = null;
                    return false;
                }
                CurrentMusic = musicName;
                m_isMusicPaused = false;
                m_musicIndex = 0;
                foreach (string music in m_Songs.Keys)
                {
                    if (music == musicName)
                        break;
                    m_musicIndex += 1;
                }
                MediaPlayer.IsRepeating = loop;
                MediaPlayer.Play(m_currentMusic);
            }
            return true;
        }

        public bool PlayMusic(string musicName)
        {
            return PlayMusic(musicName, false);
        }

        /// <summary>
        /// Simply pause the music
        /// </summary>
        public void PauseMusic()
        {
            if ((m_currentMusic != null) && !m_isMusicPaused)
            {
                MediaPlayer.Pause();
                m_isMusicPaused = true;
            }
        }

        /// <summary>
        /// Resume music if it was paused
        /// </summary>
        public void ResumeMusic()
        {
            if ((m_currentMusic != null) && m_isMusicPaused)
            {
                MediaPlayer.Resume();
                m_isMusicPaused = false;
                if (m_fadeBetweenMusic)
                {
                    MediaPlayer.Volume = 0.0f;
                    FadeUp();
                }
            }
        }

        /// <summary>
        /// Stop the playing music
        /// </summary>
        public void StopMusic()
        {
            if ((m_currentMusic != null) && (MediaPlayer.State != MediaState.Stopped))
            {
                MediaPlayer.Stop();
                m_isMusicPaused = false;
            }
        }

        /// <summary>
        /// Jump to the next music in the playlist, return to the beginning if the repeat option
        /// is set to ALL, or at a random music if it's set to SHUFFLE
        /// </summary>
        public void NextMusic()
        {
            if ((m_playOption == EPlayOption.SHUFFLE) && (m_Songs.Count >= 2))
            {
                int musicIndex;
                while ((musicIndex = m_rand.Next(0, m_Songs.Count)) == m_musicIndex);
                m_musicIndex = musicIndex;
            }
            else if (m_repeatOption == ERepeatOption.ALL)
            {
                m_musicIndex += 1;
                if (m_musicIndex >= m_Songs.Count)
                    m_musicIndex = 0;
            }
            else
            {
                m_musicIndex += 1;
                if (m_musicIndex >= m_Songs.Count)
                    m_musicIndex = m_Songs.Count - 1;
                if (m_Songs.Count <= 0)
                    StopMusic();
            }
            if (m_Songs.Count > 0)
            {
                PlayMusic(m_Songs.Keys.ElementAt(m_musicIndex));
                if (m_fadeBetweenMusic)
                {
                    MediaPlayer.Volume = 0.0f;
                    FadeUp();
                }
            }
        }

        /// <summary>
        /// Jump to previous music in the playlist, same as NextMusic for the repeat options 
        /// and play options
        /// </summary>
        public void PreviousMusic()
        {
            if ((m_playOption == EPlayOption.SHUFFLE) && (m_Songs.Count >= 2))
            {
                int musicIndex;
                while ((musicIndex = m_rand.Next(0, m_Songs.Count)) == m_musicIndex);
                m_musicIndex = musicIndex;
            }
            else if (m_repeatOption == ERepeatOption.ALL)
            {
                m_musicIndex -= 1;
                if (m_musicIndex < 0)
                    m_musicIndex = m_Songs.Count - 1;
            }
            else
            {
                m_musicIndex -= 1;
                if (m_musicIndex < 0)
                    m_musicIndex = m_musicIndex = 0;
                if (m_Songs.Count <= 0)
                    StopMusic();
            }
            if (m_Songs.Count > 0)
            {
                PlayMusic(m_Songs.Keys.ElementAt(m_musicIndex));
                if (m_fadeBetweenMusic)
                {
                    MediaPlayer.Volume = 0.0f;
                    FadeUp();
                }
            }
        }

        /// <summary>
        /// This is the main function for the fade effect, you can fade up or out with the 
        /// target volume parameter
        /// </summary>
        /// <param name="targetVolume">Volume at the end of the fade effect</param>
        /// <param name="duration">Duration of the fade</param>
        /// <returns>Return false if the duration is zero, true otherwise</returns>
        public bool FadeMusic(float targetVolume, TimeSpan duration)
        {
            if (duration <= TimeSpan.Zero)
                return false;
            m_MusicFadeEffect = new SMusicFadeEffect(MediaPlayer.Volume, targetVolume, duration);
            m_isMusicFading = true;
            return true;
        }

        /// <summary>
        /// Fade down music until his volume is 0
        /// </summary>
        /// <returns>False if the fade duration is zero, true otherwise</returns>
        public bool FadeDown()
        {
            return FadeMusic(0.0f, m_fadeDuration);
        }

        /// <summary>
        /// Fade down until the volume is 0
        /// </summary>
        /// <param name="duration">Duration of the fade effect</param>
        /// <returns>False if the fade duration is zero, true otherwise</returns>
        public bool FadeDown(TimeSpan duration)
        {
            return FadeMusic(0.0f, duration);
        }

        /// <summary>
        /// Fade up the music until his previous volume
        /// </summary>
        /// <returns>False if the fade duration is zero, true otherwise</returns>
        public bool FadeUp()
        {
            return FadeMusic(m_volume, m_fadeDuration);
        }

        /// <summary>
        /// Fade up the music to his previous volume
        /// </summary>
        /// <param name="duration">Duration of the fade effect</param>
        /// <returns>False is the fade duration is zero, true otherwise</returns>
        public bool FadeUp(TimeSpan duration)
        {
            return FadeMusic(m_volume, duration);
        }

        /// <summary>
        /// Cancel a fade effect
        /// </summary>
        /// <param name="fadeOption">Specify if the volume of the music when cancelling 
        /// should be the source or target volume. Default parameter is the source volume</param>
        public void CancelFadeMusic(EFadeOption fadeOption)
        {
            if (m_isMusicFading)
            {
                switch (fadeOption)
                {
                    case EFadeOption.SOURCE: MediaPlayer.Volume = m_MusicFadeEffect.m_sourceVolume; break;
                    case EFadeOption.TARGET: MediaPlayer.Volume = m_MusicFadeEffect.m_targetVolume; break;
                }
                m_isMusicFading = false;
            }
        }

        public void CancelFadeMusic()
        {
            CancelFadeMusic(EFadeOption.SOURCE);
        }

    #endregion

    }
}
